package breaker

import (
	"sync"
	"time"

	xtime "abm-cache-center/time"
)

const (
	// StateOpen when circuit breaker open, request not allowed, after sleep
	// some duration, allow one single request for testing the health, if ok
	// then state reset to closed, if not continue the step.
	StateOpen int32 = iota
	// StateClosed when circuit breaker closed, request allowed, the breaker
	// calc the succeed ratio, if request num greater request setting and
	// ratio lower than the setting ratio, then reset state to open.
	StateClosed
	// StateHalfopen when circuit breaker open, after slepp some duration, allow
	// one request, but not state closed.
	StateHalfopen

	_switchOn int32 = iota
	_switchOff
)

// Config broker config.
type Config struct {
	Window    xtime.Duration
	Sleep     xtime.Duration
	Bucket    int
	Ratio     float32
	Request   uint64
	SwitchOff bool // breaker switch,default off.

	// fast : fast stop & fast start
	// slow  : slow stop & slow start
	Pattern string // "fast" or "slow"
}

// Breaker is a CircuitBreaker pattern.
type Breaker interface {
	Allow() error
	MarkSuccess()
	MarkFailed()
	IsOpen() bool
}

// Group represents a class of CircuitBreaker and forms a namespace in which
// units of CircuitBreaker.
type Group struct {
	name string

	mu   sync.RWMutex
	brks map[string]Breaker
	conf *Config
}

// newBreaker new a breaker.
func newBreaker(group, key string, c *Config) Breaker {
	switch c.Pattern {
	case "slow":
		// TODO
		return nil
	default:
		breaker := &hystrixBreaker{
			count:   newWindow(time.Duration(c.Window), c.Bucket),
			state:   StateClosed,
			last:    time.Now().UnixNano(),
			ratio:   c.Ratio,
			request: c.Request,
			sleep:   int64(time.Duration(c.Sleep)),
			on:      _switchOff,

			group: group,
			name:  key,
		}
		if c.SwitchOff {
			breaker.on = _switchOn
		}
		return breaker
	}
}

// NewGroup new a breaker group container, if conf nil use default conf.
func NewGroup(name string, conf *Config) *Group {
	if conf == nil {
		_mu.RLock()
		conf = _conf
		_mu.RUnlock()
	}
	return &Group{
		name: name,
		conf: conf,
		brks: make(map[string]Breaker),
	}
}

// Get get a breaker by a specified key, if breaker not exists then make a new one.
func (g *Group) Get(key string) Breaker {
	g.mu.RLock()
	brk, ok := g.brks[key]
	conf := g.conf
	g.mu.RUnlock()
	if ok {
		return brk
	}
	// NOTE here may new multi breaker for rarely case, let gc drop it.
	brk = newBreaker(g.name, key, conf)
	g.mu.Lock()
	if _, ok = g.brks[key]; !ok {
		g.brks[key] = brk
	}
	g.mu.Unlock()
	return brk
}

// Reload reload the group by specified config, this may let all inner breaker
// reset to a new one.
func (g *Group) Reload(conf *Config) {
	if conf == nil {
		return
	}
	g.mu.Lock()
	g.conf = conf
	g.brks = make(map[string]Breaker, len(g.brks))
	g.mu.Unlock()
}

// Go runs your function while tracking the breaker state of group.
func (g *Group) Go(name string, run, fallback func() error) error {
	breaker := g.Get(name)
	if err := breaker.Allow(); err != nil {
		return fallback()
	}
	return run()
}

var (
	_mu   sync.RWMutex
	_conf = &Config{
		Window:  xtime.Duration(3 * time.Second),
		Sleep:   xtime.Duration(100 * time.Millisecond),
		Bucket:  10,
		Ratio:   0.5,
		Request: 100,
	}
	_group = NewGroup("default", _conf)
)

// Init init global breaker config, also can reload config after first time call.
func Init(conf *Config) {
	if conf == nil {
		return
	}
	_mu.Lock()
	_conf = conf
	_mu.Unlock()
}

// Go runs your function while tracking the breaker state of default group.
func Go(name string, run, fallback func() error) error {
	breaker := _group.Get(name)
	if err := breaker.Allow(); err != nil {
		return fallback()
	}
	return run()
}
